﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WordNet.Core.ElementContracts;
using WordNet.Core.Extensions;

namespace WordNet.Core
{
    public class SenseKey:ISenseKey
    {
        private readonly String _lemma;
        private readonly int _lexID;
        private readonly POS _pos;
        private readonly bool _isAdjSat;
        private readonly ILexFile _lexFile;

        private bool _isHeadSet;
        private String _headLemma = null;
        private int _headLexID = -1;
        private String _toString;

        public SenseKey(String lemma, int lexID, ISynset synset)
            : this(lemma, lexID, synset.PartOfSpeech, synset.IsAdjectiveSatellite, synset.LexicalFile)
        {
            ;
        }

        public SenseKey(String lemma, int lexID, POS pos, bool isAdjSat, ILexFile lexFile, String originalKey)
            : this(lemma, lexID, pos, isAdjSat, lexFile)
        {

            if (originalKey == null)
                throw new ArgumentNullException();
            _toString = originalKey;
        }

        public SenseKey(String lemma, int lexID, POS pos, ILexFile lexFile, String headLemma, int headLexID, String originalKey)
            : this(lemma, lexID, pos, (headLemma != null), lexFile)
        {
            ;
            if (headLemma == null)
            {
                _isHeadSet = true;
            }
            else
            {
                SetHead(headLemma, headLexID);
            }
            if (originalKey == null)
                throw new ArgumentNullException();
            _toString = originalKey;
        }

        public SenseKey(String lemma, int lexID, POS pos, bool isAdjSat, ILexFile lexFile)
        {
            if (pos == null)
                throw new ArgumentNullException();
            if (lexFile == null)
                throw new ArgumentNullException();

            // all sense key lemmas are in lower case
            // also checks for null
            _lemma = lemma.ToLower();
            _lexID = lexID;
            _pos = pos;
            _isAdjSat = isAdjSat;
            _lexFile = lexFile;
            _isHeadSet = !isAdjSat;
        }
        
        public string Lemma
        {
            get { return _lemma; }
        }

        public int SynsetType
        {
            get { return _isAdjSat ? 5 : _pos.Number; }
        }

        public bool IsAdjectiveSatellite
        {
            get { return _isAdjSat; }
        }

        public ILexFile LexicalFile
        {
            get { return _lexFile; }
        }

        public int LexicalID
        {
            get { return _lexID; }
        }

        public string HeadWord
        {
            get { return _headLemma; }
        }

        public int HeadID
        {
            get { return _headLexID; }
        }

        public void SetHead(string headLemma, int headLexID)
        {
            headLexID.CheckLexicalID();
            if (headLemma.Trim().Length == 0)
                throw new ArgumentException();
            _headLemma = headLemma;
            _headLexID = headLexID;
            _isHeadSet = true;
        }

        public bool NeedsHeadSet
        {
            get { return !_isHeadSet; }
        }

        public POS PartOfSpeech
        {
            get { return _pos; }
        }

        public int CompareTo(ISenseKey key)
        {
            int cmp;

            // first sort alphabetically by lemma
            cmp = this.Lemma.CompareTo(key.Lemma);
            if (cmp != 0)
                return cmp;

            // then sort by synset type
            cmp = this.SynsetType.CompareTo(key.SynsetType);
            if (cmp != 0)
                return cmp;

            // then sort by lex_filenum
            cmp = this.LexicalFile.Number.CompareTo(key.LexicalFile.Number);
            if (cmp != 0)
                return cmp;

            // then sort by lex_id
            cmp = this.LexicalID.CompareTo(key.LexicalID);
            if (cmp != 0)
                return cmp;

            if (!this.IsAdjectiveSatellite && !key.IsAdjectiveSatellite)
                return 0;
            if (!this.IsAdjectiveSatellite & key.IsAdjectiveSatellite)
                return -1;
            if (this.IsAdjectiveSatellite & !key.IsAdjectiveSatellite)
                return 1;

            // then sort by head_word
            cmp = this.HeadWord.CompareTo(key.HeadWord);
            if (cmp != 0)
                return cmp;

            // finally by head_id
            return this.HeadID.CompareTo(key.HeadID);
        }

        public override string ToString()
        {
            if (_toString == null)
                _toString = ConvertToString(this);
            return _toString;
        }

        public override int GetHashCode()
        {
            const int prime = 31;
            int result = 1;
            result = prime * result + _lemma.GetHashCode();
            result = prime * result + _lexID;
            result = prime * result + _pos.GetHashCode();
            result = prime * result + _lexFile.GetHashCode();
            result = prime * result + (_isAdjSat ? 1231 : 1237);
            if (_isAdjSat)
            {
                result = prime * result + (_headLemma == null ? 0 : _headLemma.GetHashCode());
                result = prime * result + _headLexID;
            }
            return result;
        }
       
        public override bool Equals(object obj)
        {
            if (Object.ReferenceEquals(obj, null))
                return false;
            if (Object.ReferenceEquals(this, obj))
                return true;
            SenseKey other = obj as SenseKey;
            if(other == null)
                return false;
            
            if (!_lemma.Equals(other.Lemma))
                return false;
            if (_lexID != other.LexicalID)
                return false;
            if (_pos != other.PartOfSpeech)
                return false;
            if (_lexFile.Number != other.LexicalFile.Number)
                return false;
            if (_isAdjSat != other.IsAdjectiveSatellite)
                return false;
            if (_isAdjSat)
            {
                if (!_headLemma.Equals(other.HeadWord))
                    return false;
                if (_headLexID != other.HeadID)
                    return false;
            }
            return true;
        }

        public static String ConvertToString(ISenseKey key)
        {
            // figure out appropriate size
            int size = key.Lemma.Length + 10;
            if (key.IsAdjectiveSatellite)
                size += key.HeadWord.Length + 2;

            // allocate builder
            StringBuilder sb = new StringBuilder(size);

            // make string
            sb.Append(key.Lemma.ToLower());
            sb.Append('%');
            sb.Append(key.SynsetType);
            sb.Append(':');
            sb.Append(key.LexicalFile.Number.GetLexicalFileNumberString());
            sb.Append(':');
            sb.Append(key.LexicalID.GetLexicalIDForSenseKey());
            sb.Append(':');
            if (key.IsAdjectiveSatellite)
            {
                if (key.NeedsHeadSet)
                {
                    sb.Append("??");
                }
                else
                {
                    sb.Append(key.HeadWord);
                }
            }
            sb.Append(':');
            if (key.IsAdjectiveSatellite)
            {
                if (key.NeedsHeadSet)
                {
                    sb.Append("??");
                }
                else
                {
                    sb.Append(key.HeadID.GetLexicalIDForSenseKey());
                }
            }
            return sb.ToString();
        }
    }
}
